package Cryptology;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

public class BagEncript {

    private static int[] privateKeys;// 私钥数组
    private static int sn; // 私钥数组所有元素和
    private static int[] publicKeys;// 公钥数组
    private static int m = 1; // 模数m
    private static int e = 1; // 乘数e
    private static int d = 1; // e对m的模反元素d,满足e*d % m == 1恒成立
    private static String lastEightBinaryString; // 明文二进制数的后八位
    // ,明文的二进制每一位都和密钥数组是一一对应的,所以必须将过长的明文分割成与密钥等长的子数组
    private static char[] charLBS;// 明文二进制数的后八位 对应的字符数组
    private static List<Integer> listOfCipherPieces; // 存放密文片段的集合
    private static List<char[]> listOfCharLBS; // listOfCharLBS用于存放明文片段的二进制数组
    private static String resultPlaintext = "";// 解密密文后得到的二进制结果

    public static void main(String[] args) {
        // 1、生成超递增背包私钥
        createPriKey();
        // 2、取乘数e和模数m,他们的模反 d
        getEAndM();
        // 3、生成公钥
        createPubKey();
        // 4、加密,解密
        encript(Integer.parseInt(args[0]));

    }

    // 生成私钥4位超递增背包
    private static void createPriKey() {

        privateKeys = new int[8];
        int num1 = 1;
        int num2 = 1;
        do {
            num1 = (int) (Math.random() * 5);
            num2 = (int) (Math.random() * 5);
        } while (num1 == num2 || num1 == 0 || num2 == 0);
        if (num1 > num2) {
            int temp = num1;
            num1 = num2;
            num2 = temp;
        }
        privateKeys[0] = num1;
        privateKeys[1] = num2;
        int num3 = 1;
        for (int i = 2; i < privateKeys.length; i++) {
            do {
                num3 = (int) (Math.random() * Math.pow(3, i));
            } while (num3 <= (privateKeys[i - 2] + privateKeys[i - 1]));
            privateKeys[i] = num3;
        }
        for (int i = 0; i < privateKeys.length; i++) {
            sn += privateKeys[i];
            System.out.println("privateKeys[" + i + "] = " + privateKeys[i]);
        }
    }

    // 2、取乘数e和模数m
    private static void getEAndM() {

        do {
            m = (int) (Math.random() * 10000);
        } while (sn >= m);

        do {
            e = (int) (Math.random() * 10000);
        } while (gcd(e, m) != 1 || e > 10000);
        loop: for (int i = 0;; i++) {
            if ((i * m + 1) % e == 0) {
                d = (i * m + 1) / e;
                break loop;
            }
        }
        System.out.println("m = " + m + " , e = " + e + " ,d = " + d);
    }

    // 3、生成公钥
    private static void createPubKey() {
        publicKeys = new int[privateKeys.length];
        for (int i = 0; i < publicKeys.length; i++) {
            publicKeys[i] = (e * privateKeys[i]) % m;
            System.out.println("publicKeys[" + i + "] = " + publicKeys[i]);
        }
    }

    // 4、加密
    private static void encript(int msg) {
        System.out.println("要加密的明文 ： " + msg);
        // 初始化listOfCharLBS,用于存放明文片段的二进制数组
        listOfCharLBS = new ArrayList<char[]>();
        // 初始化 listOfCipherPieces,是明文片段加密后对应的密文片段的存放数组
        listOfCipherPieces = new ArrayList<Integer>();
        String binaryString = Integer.toBinaryString(msg);
        // 判断二进制数组是否是8位或8的整数倍
        int leftZero = binaryString.length() % 8;
        // 将不足8位或8位整数倍的左边二进制位补0
        for (int i = 0; i < 8 - leftZero; i++) {
            binaryString = "0" + binaryString;
        }
        System.out.println("明文的二进制数组  ：  " + binaryString);
        // 计算明文二进制字符串按照8位一组可以分机组
        int k = binaryString.length() / 8;
        for (int i = 0; i < k; i++) {
            // 从二进制数组的右边开始向左,每8位一组
            lastEightBinaryString = binaryString.substring(
                    binaryString.length() - 8, binaryString.length());
            // 将二进制数组重新赋值,新数组中只要原数组的前 n - 8位(其中n代表原数组长度)
            binaryString = binaryString.substring(0, binaryString.length() - 8);
            // 将二进制片段存入list中
            charLBS = lastEightBinaryString.toCharArray();
            listOfCharLBS.add(0, charLBS);
        }
        for (int j = 0; j < listOfCharLBS.size(); j++) {
            char[] eightBinary = (char[]) listOfCharLBS.get(j);
            System.out.println("第" + j + "个char 数组 = "
                    + String.copyValueOf(eightBinary));
            int cipherPart = 0; // 密文片段
            // 加密过程 每个二进制位上的数乘以公钥对应位置的元素,之后求和,就是当前明文片段对应的密文
            for (int i = 0; i < eightBinary.length; i++) {
                cipherPart += publicKeys[i]
                        * Integer.parseInt(eightBinary[i] + "");
            }
            // 密文片段放入密文数组
            listOfCipherPieces.add(0, cipherPart);
        }
        // 输出查看密文数组
        for (int i = 0; i < listOfCipherPieces.size(); i++) {
            Integer cipherPart = listOfCipherPieces.get(i);
            System.out.println("密文片段 " + i + " = " + cipherPart);
        }
        // 解密
        System.out.println("密文数组长度 = " + listOfCipherPieces.size());

        for (int i = 0; i < listOfCipherPieces.size(); i++) {
            int cipherPart = (Integer) listOfCipherPieces.get(i);
            // 解密方法
            decript(cipherPart);
        }
        // 打印解密后的明文
        System.out.println("解密后的明文二进制数组 ： " + resultPlaintext);

        // for (int i = 0; i < listOfPlaintext.size(); i++) {
        // System.out.print(listOfPlaintext.get(i));
        // }
        // 将二进制结果转成10进制输出
        getPlainText();
        resultPlaintext = null;
    }

    // 解密
    public static void decript(int cipherPart) {

        int plainText = cipherPart * d % m; // 密文片段解密后得到明文片段
        int cha = plainText;

        for (int i = privateKeys.length - 1; i >= 0; i--) {

            if (cha >= privateKeys[i]) {
                // listOfPlaintext.add(0, 1);
                resultPlaintext = "1" + resultPlaintext;
                cha = cha - privateKeys[i];
            } else {
                // listOfPlaintext.add(0, 0);
                resultPlaintext = "0" + resultPlaintext;
            }
        }
    }

    // 将二进制结果转成10进制输出
    public static void getPlainText() {
        BigInteger bi = new BigInteger(resultPlaintext, 2);
        System.out.println(bi.toString(10));
    }

    // 求最大公约数
    public static int gcd(int a, int b) {
        int gcd;
        if (b == 0)
            gcd = a;
        else
            gcd = gcd(b, a % b);
        return gcd;

    }
}